Setup

Packages

First we load up the packages we’ll be working with

# remotes::install_github("mathesong/bloodstream")
# remotes::install_github("mathesong/kinfitr")

library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(bloodstream)
library(kinfitr)
library(job)

theme_set(theme_light())

Data

download.file("https://www.dropbox.com/scl/fi/ax1t3lfop3pp3h4cir0m7/ds004869.zip?rlkey=6z7e9wopud9ngu26ekbd3kfks&dl=1", 
              destfile = "ds004869.zip")

unzip(zipfile = "ds004869.zip")

Blood Analysis

job({ 
  bloodstream("ds004869/") 
})

Alternatively, if you want to use a configuration file

bloodstream("ds004869/", configpath = "ds004869/code/config_tutorial.json") 

Loading Data

Loading bloodstream outputs

We want the arterial input function data, which are called “input”.

bloodstream_data <- bloodstream_import_inputfunctions("ds004869/derivatives/bloodstreamtutorial/") %>%
  select(-measurement)
head(bloodstream_data, n = 6)

bloodstream_data %>% 
  slice(1:6) %>% 
  pull(input) %>% 
  map(plot)
[[1]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[2]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[3]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[4]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[5]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[6]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

Loading petprep outputs (TACs and morphology)

petprep_data <- kinfitr::bids_parse_files("ds004869/derivatives/petprep_extract_tacs/") %>% 
  unnest(filedata) %>% 
  filter(str_detect(path_absolute, "gtmseg"))

tacs <- petprep_data %>% 
  filter(measurement=="tacs") %>% 
  filter(is.na(pvc)) %>% 
  mutate(tacdata = map(path_absolute, ~read_delim(.x, delim="\t", 
                                                  show_col_types = FALSE)))

morphdata <- petprep_data %>%
  filter(measurement=="morph") %>%
  mutate(morphdata = map(path_absolute, ~read_delim(.x, delim="\t",
                                                  show_col_types = FALSE)))

Here, we would usually combine TACs according to which combined regions we were interested in, and use volume-weighted averaging. In this case, we’ll just skip that step and be a little bit naughty and focus on one region: the left putamen.

Creating combined TACs

Now we want to combine regions by volume-weighted averages of the contitutent regions. We’ll create a frontal cortex region, a striatal region and a hippocampus-amygdala region.

First, let’s define the regions

frontal_regions <- morphdata$morphdata[[1]] %>% 
  filter(str_detect(name, "frontal")) %>% 
  pull(name)

frontal_regions
 [1] "ctx-lh-caudalmiddlefrontal"  "ctx-lh-lateralorbitofrontal" "ctx-lh-medialorbitofrontal"  "ctx-lh-rostralmiddlefrontal"
 [5] "ctx-lh-superiorfrontal"      "ctx-lh-frontalpole"          "ctx-rh-caudalmiddlefrontal"  "ctx-rh-lateralorbitofrontal"
 [9] "ctx-rh-medialorbitofrontal"  "ctx-rh-rostralmiddlefrontal" "ctx-rh-superiorfrontal"      "ctx-rh-frontalpole"         
striatal_regions <- morphdata$morphdata[[1]] %>% 
  filter(str_detect(name, "Putamen|Accumbens|Caudate")) %>% 
  pull(name)

striatal_regions
[1] "Left-Caudate"         "Left-Putamen"         "Left-Accumbens-area"  "Right-Caudate"        "Right-Putamen"        "Right-Accumbens-area"
hipamg_regions <- morphdata$morphdata[[1]] %>% 
  filter(str_detect(name, "Hippocampus|Amygdala")) %>% 
  pull(name)

hipamg_regions
[1] "Left-Hippocampus"  "Left-Amygdala"     "Right-Hippocampus" "Right-Amygdala"   

Now we combine the TACs and the region sizes

selected_tacs <- select(tacs, c(ses:rec, tacdata)) %>% 
  inner_join(select(morphdata, c(ses:rec, morphdata))) %>% 
  group_by(sub, ses) %>% 
  mutate(tacdata = map(tacdata, ~pivot_longer(.x, 
                                              cols = `Left-Cerebral-White-Matter`:`ctx-rh-insula`, 
                                              names_to = "name", values_to = "TAC"))) %>% 
  mutate(tacdata = map2(tacdata, morphdata, ~inner_join(.x, .y, by="name")))
Joining with `by = join_by(ses, sub, task, trc, acq, run, rec)`

Then we perform volume-weighted averaging

volume_weighted_average_tac <- function(tacdata, regions, regionname) {
  
  tacdata_combined <- tacdata %>% 
    filter(name %in% regions) %>% 
    group_by(frame_start, frame_end) %>% 
    mutate(volume_tot = sum(`volume-mm3`),
           volume_frac = `volume-mm3` / volume_tot,
           TAC_frac = TAC * volume_frac) %>% 
    summarise(!!regionname := sum(TAC_frac), 
              .groups = "keep") %>% 
    ungroup()
  
  return(tacdata_combined)
  
}

volume_weighted_average_tacs <- function(tacdata, regions_list) {
  
  regionnames <- names(regions_list)
  
  out <- map2(regions_list, regionnames, ~volume_weighted_average_tac(tacdata, .x, .y))
  
  out <- purrr::reduce(out, dplyr::inner_join, by = c("frame_start",
                                                      "frame_end"))
  
  return(out)
}

regions_list <- list(
  Frontal = frontal_regions,
  Striatum = striatal_regions,
  HippAmg = hipamg_regions
)


selected_tacs <- selected_tacs %>% 
  group_by(sub, ses) %>% 
  mutate(selected_tacdata = map(tacdata, ~volume_weighted_average_tacs(.x, 
                                                                       regions_list)))
  

Combining the TAC and blood (input) data

modeldata <- selected_tacs %>% 
  select(ses:rec, tacdata = selected_tacdata) %>% 
  inner_join(bloodstream_data)
Joining with `by = join_by(ses, sub, task, trc, acq, run, rec)`

Fitting TACs

Preparation

Correcting Units

The TAC data are in Bq/mL, and the bloodstream data are in kBq/mL. So we correct this.

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(across(.cols = Frontal:HippAmg, 
                                       ~unit_convert(.x, from_units = "Bq", to_units = "kBq")))))

Adding frame midtimes and durations

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(frame_start = frame_start / 60,
                                frame_end = frame_end / 60) %>% 
                         mutate(frame_dur = frame_end - frame_start,
                                frame_mid = frame_start + 0.5*frame_dur)))

Adding weights

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(meanTAC = rowMeans( .x %>% select(Frontal:HippAmg) )) %>% 
                         mutate(weights = weights_create(t_start = frame_start,
                                                         t_end = frame_end,
                                                         tac = meanTAC))))

Fitting a single TAC

fit_2tc <- twotcm(
       t_tac = modeldata$tacdata[[1]]$frame_mid, 
       tac = modeldata$tacdata[[1]]$Frontal, 
       input = modeldata$input[[1]], 
       weights = modeldata$tacdata[[1]]$weights, 
       vB = 0.05, multstart_iter = 5)

fit_2tc$par

plot(fit_2tc)

Fitting multiple TACs

Here we’ll use a linearised model because they fit more quickly, in this case Logan. But most linearised models require a t* value to operate.

Selecting a t* value

Let’s choose an appropriate t* value

modeldata %>% 
  ungroup() %>% 
  filter(ses=="baseline") %>% 
  slice(1:5) %>% 
  mutate(tstarplot = map2(tacdata, input, 
     ~Logan_tstar(
         t_tac = .x$frame_mid, 
         lowroi =  .x$HippAmg,
         medroi =  .x$Frontal,
         highroi = .x$Striatum, 
         input = .y,
         vB = 0.05)
     )) %>% 
  pull(tstarplot)
Warning: There were 15 warnings in `mutate()`.
The first warning was:
ℹ In argument: `tstarplot = map2(...)`.
Caused by warning:
! Removed 1 row containing missing values or values outside the scale range (`geom_point()`).
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 14 remaining warnings.
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

Ok, let’s use 13 frames.

Fitting

Let’s focus on the frontal cortex

modeldata <- modeldata %>% 
  group_by(sub, ses) %>% 
  mutate(Logan_fit = map2(tacdata, input, ~Loganplot(t_tac = .x$frame_mid,
                                             tac = .x$Frontal, 
                                             input= .y, 
                                             tstarIncludedFrames = 13, 
                                             weights = .x$weights, 
                                             vB = 0.05, 
                                             dur = .x$frame_dur)))

Plotting

Let’s see a few fits

map(modeldata[1:6,]$Logan_fit, plot)
[[1]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[2]]
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_point()`).

[[3]]
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_point()`).

[[4]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[5]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[6]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

Outcomes

Logan_outcomes <- modeldata %>% 
  select(sub, ses, Logan_fit) %>% 
  mutate(Vt = map_dbl(Logan_fit, c("par", "Vt"))) %>% 
  select(-Logan_fit)

ggplot(Logan_outcomes, aes(x=ses, y=Vt, colour=sub, group=sub)) +
  geom_point(size=3, colour="black") +
  geom_point(size=2.5) +
  geom_line() + 
  scale_y_log10()

LS0tCnRpdGxlOiAiT0hCTTIwMjQgVHV0b3JpYWw6IFRBQyBEYXRhIEhhbmRzLW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIFNldHVwCgojIyBQYWNrYWdlcwoKRmlyc3Qgd2UgbG9hZCB1cCB0aGUgcGFja2FnZXMgd2UnbGwgYmUgd29ya2luZyB3aXRoCgpgYGB7cn0KIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigibWF0aGVzb25nL2Jsb29kc3RyZWFtIikKIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigibWF0aGVzb25nL2tpbmZpdHIiKQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoYmxvb2RzdHJlYW0pCmxpYnJhcnkoa2luZml0cikKbGlicmFyeShqb2IpCgp0aGVtZV9zZXQodGhlbWVfbGlnaHQoKSkKYGBgCgoKIyMgRGF0YQoKYGBge3IsIGV2YWw9RkFMU0V9CmRvd25sb2FkLmZpbGUoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3NjbC9maS9heDF0M2xmb3AzcHAzaDRjaXIwbTcvZHMwMDQ4NjkuemlwP3Jsa2V5PTZ6N2U5d29wdWQ5bmd1MjZla2JkM2tma3MmZGw9MSIsIAogICAgICAgICAgICAgIGRlc3RmaWxlID0gImRzMDA0ODY5LnppcCIpCgp1bnppcCh6aXBmaWxlID0gImRzMDA0ODY5LnppcCIpCmBgYAoKCiMgQmxvb2QgQW5hbHlzaXMKCmBgYHtyLCBldmFsPUZBTFNFfQpqb2IoeyAKICBibG9vZHN0cmVhbSgiZHMwMDQ4NjkvIikgCn0pCmBgYAoKQWx0ZXJuYXRpdmVseSwgaWYgeW91IHdhbnQgdG8gdXNlIGEgY29uZmlndXJhdGlvbiBmaWxlCgpgYGB7ciwgZXZhbD1GQUxTRX0KYmxvb2RzdHJlYW0oImRzMDA0ODY5LyIsIGNvbmZpZ3BhdGggPSAiZHMwMDQ4NjkvY29kZS9jb25maWdfdHV0b3JpYWwuanNvbiIpIApgYGAKCgoKIyBMb2FkaW5nIERhdGEKCiMjIExvYWRpbmcgYmxvb2RzdHJlYW0gb3V0cHV0cwoKV2Ugd2FudCB0aGUgYXJ0ZXJpYWwgaW5wdXQgZnVuY3Rpb24gZGF0YSwgd2hpY2ggYXJlIGNhbGxlZCAiaW5wdXQiLgoKCmBgYHtyfQpibG9vZHN0cmVhbV9kYXRhIDwtIGJsb29kc3RyZWFtX2ltcG9ydF9pbnB1dGZ1bmN0aW9ucygiZHMwMDQ4NjkvZGVyaXZhdGl2ZXMvYmxvb2RzdHJlYW10dXRvcmlhbC8iKSAlPiUKICBzZWxlY3QoLW1lYXN1cmVtZW50KQpgYGAKCmBgYHtyfQpoZWFkKGJsb29kc3RyZWFtX2RhdGEsIG4gPSA2KQoKYmxvb2RzdHJlYW1fZGF0YSAlPiUgCiAgc2xpY2UoMTo2KSAlPiUgCiAgcHVsbChpbnB1dCkgJT4lIAogIG1hcChwbG90KQpgYGAKCgoKIyMgTG9hZGluZyBwZXRwcmVwIG91dHB1dHMgKFRBQ3MgYW5kIG1vcnBob2xvZ3kpCgpgYGB7cn0KcGV0cHJlcF9kYXRhIDwtIGtpbmZpdHI6OmJpZHNfcGFyc2VfZmlsZXMoImRzMDA0ODY5L2Rlcml2YXRpdmVzL3BldHByZXBfZXh0cmFjdF90YWNzLyIpICU+JSAKICB1bm5lc3QoZmlsZWRhdGEpICU+JSAKICBmaWx0ZXIoc3RyX2RldGVjdChwYXRoX2Fic29sdXRlLCAiZ3Rtc2VnIikpCgp0YWNzIDwtIHBldHByZXBfZGF0YSAlPiUgCiAgZmlsdGVyKG1lYXN1cmVtZW50PT0idGFjcyIpICU+JSAKICBmaWx0ZXIoaXMubmEocHZjKSkgJT4lIAogIG11dGF0ZSh0YWNkYXRhID0gbWFwKHBhdGhfYWJzb2x1dGUsIH5yZWFkX2RlbGltKC54LCBkZWxpbT0iXHQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvbF90eXBlcyA9IEZBTFNFKSkpCgptb3JwaGRhdGEgPC0gcGV0cHJlcF9kYXRhICU+JQogIGZpbHRlcihtZWFzdXJlbWVudD09Im1vcnBoIikgJT4lCiAgbXV0YXRlKG1vcnBoZGF0YSA9IG1hcChwYXRoX2Fic29sdXRlLCB+cmVhZF9kZWxpbSgueCwgZGVsaW09Ilx0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvbF90eXBlcyA9IEZBTFNFKSkpCmBgYAoKCkhlcmUsIHdlIHdvdWxkIHVzdWFsbHkgY29tYmluZSBUQUNzIGFjY29yZGluZyB0byB3aGljaCBjb21iaW5lZCByZWdpb25zIHdlIHdlcmUgaW50ZXJlc3RlZCBpbiwgYW5kIHVzZSB2b2x1bWUtd2VpZ2h0ZWQgYXZlcmFnaW5nLiAgSW4gdGhpcyBjYXNlLCB3ZSdsbCBqdXN0IHNraXAgdGhhdCBzdGVwIGFuZCBiZSBhIGxpdHRsZSBiaXQgbmF1Z2h0eSBhbmQgZm9jdXMgb24gb25lIHJlZ2lvbjogdGhlIGxlZnQgcHV0YW1lbi4KCiMjIENyZWF0aW5nIGNvbWJpbmVkIFRBQ3MKCk5vdyB3ZSB3YW50IHRvIGNvbWJpbmUgcmVnaW9ucyBieSB2b2x1bWUtd2VpZ2h0ZWQgYXZlcmFnZXMgb2YgdGhlIGNvbnRpdHV0ZW50IHJlZ2lvbnMuICBXZSdsbCBjcmVhdGUgYSBmcm9udGFsIGNvcnRleCByZWdpb24sIGEgc3RyaWF0YWwgcmVnaW9uIGFuZCBhIGhpcHBvY2FtcHVzLWFteWdkYWxhIHJlZ2lvbi4KCkZpcnN0LCBsZXQncyBkZWZpbmUgdGhlIHJlZ2lvbnMKCmBgYHtyfQpmcm9udGFsX3JlZ2lvbnMgPC0gbW9ycGhkYXRhJG1vcnBoZGF0YVtbMV1dICU+JSAKICBmaWx0ZXIoc3RyX2RldGVjdChuYW1lLCAiZnJvbnRhbCIpKSAlPiUgCiAgcHVsbChuYW1lKQoKZnJvbnRhbF9yZWdpb25zCmBgYAoKCmBgYHtyfQpzdHJpYXRhbF9yZWdpb25zIDwtIG1vcnBoZGF0YSRtb3JwaGRhdGFbWzFdXSAlPiUgCiAgZmlsdGVyKHN0cl9kZXRlY3QobmFtZSwgIlB1dGFtZW58QWNjdW1iZW5zfENhdWRhdGUiKSkgJT4lIAogIHB1bGwobmFtZSkKCnN0cmlhdGFsX3JlZ2lvbnMKYGBgCgoKYGBge3J9CmhpcGFtZ19yZWdpb25zIDwtIG1vcnBoZGF0YSRtb3JwaGRhdGFbWzFdXSAlPiUgCiAgZmlsdGVyKHN0cl9kZXRlY3QobmFtZSwgIkhpcHBvY2FtcHVzfEFteWdkYWxhIikpICU+JSAKICBwdWxsKG5hbWUpCgpoaXBhbWdfcmVnaW9ucwpgYGAKCgpOb3cgd2UgY29tYmluZSB0aGUgVEFDcyBhbmQgdGhlIHJlZ2lvbiBzaXplcwoKYGBge3J9CnNlbGVjdGVkX3RhY3MgPC0gc2VsZWN0KHRhY3MsIGMoc2VzOnJlYywgdGFjZGF0YSkpICU+JSAKICBpbm5lcl9qb2luKHNlbGVjdChtb3JwaGRhdGEsIGMoc2VzOnJlYywgbW9ycGhkYXRhKSkpICU+JSAKICBncm91cF9ieShzdWIsIHNlcykgJT4lIAogIG11dGF0ZSh0YWNkYXRhID0gbWFwKHRhY2RhdGEsIH5waXZvdF9sb25nZXIoLngsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29scyA9IGBMZWZ0LUNlcmVicmFsLVdoaXRlLU1hdHRlcmA6YGN0eC1yaC1pbnN1bGFgLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm5hbWUiLCB2YWx1ZXNfdG8gPSAiVEFDIikpKSAlPiUgCiAgbXV0YXRlKHRhY2RhdGEgPSBtYXAyKHRhY2RhdGEsIG1vcnBoZGF0YSwgfmlubmVyX2pvaW4oLngsIC55LCBieT0ibmFtZSIpKSkKYGBgCgpUaGVuIHdlIHBlcmZvcm0gdm9sdW1lLXdlaWdodGVkIGF2ZXJhZ2luZwoKYGBge3J9CnZvbHVtZV93ZWlnaHRlZF9hdmVyYWdlX3RhYyA8LSBmdW5jdGlvbih0YWNkYXRhLCByZWdpb25zLCByZWdpb25uYW1lKSB7CiAgCiAgdGFjZGF0YV9jb21iaW5lZCA8LSB0YWNkYXRhICU+JSAKICAgIGZpbHRlcihuYW1lICVpbiUgcmVnaW9ucykgJT4lIAogICAgZ3JvdXBfYnkoZnJhbWVfc3RhcnQsIGZyYW1lX2VuZCkgJT4lIAogICAgbXV0YXRlKHZvbHVtZV90b3QgPSBzdW0oYHZvbHVtZS1tbTNgKSwKICAgICAgICAgICB2b2x1bWVfZnJhYyA9IGB2b2x1bWUtbW0zYCAvIHZvbHVtZV90b3QsCiAgICAgICAgICAgVEFDX2ZyYWMgPSBUQUMgKiB2b2x1bWVfZnJhYykgJT4lIAogICAgc3VtbWFyaXNlKCEhcmVnaW9ubmFtZSA6PSBzdW0oVEFDX2ZyYWMpLCAKICAgICAgICAgICAgICAuZ3JvdXBzID0gImtlZXAiKSAlPiUgCiAgICB1bmdyb3VwKCkKICAKICByZXR1cm4odGFjZGF0YV9jb21iaW5lZCkKICAKfQoKdm9sdW1lX3dlaWdodGVkX2F2ZXJhZ2VfdGFjcyA8LSBmdW5jdGlvbih0YWNkYXRhLCByZWdpb25zX2xpc3QpIHsKICAKICByZWdpb25uYW1lcyA8LSBuYW1lcyhyZWdpb25zX2xpc3QpCiAgCiAgb3V0IDwtIG1hcDIocmVnaW9uc19saXN0LCByZWdpb25uYW1lcywgfnZvbHVtZV93ZWlnaHRlZF9hdmVyYWdlX3RhYyh0YWNkYXRhLCAueCwgLnkpKQogIAogIG91dCA8LSBwdXJycjo6cmVkdWNlKG91dCwgZHBseXI6OmlubmVyX2pvaW4sIGJ5ID0gYygiZnJhbWVfc3RhcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZnJhbWVfZW5kIikpCiAgCiAgcmV0dXJuKG91dCkKfQoKcmVnaW9uc19saXN0IDwtIGxpc3QoCiAgRnJvbnRhbCA9IGZyb250YWxfcmVnaW9ucywKICBTdHJpYXR1bSA9IHN0cmlhdGFsX3JlZ2lvbnMsCiAgSGlwcEFtZyA9IGhpcGFtZ19yZWdpb25zCikKCgpzZWxlY3RlZF90YWNzIDwtIHNlbGVjdGVkX3RhY3MgJT4lIAogIGdyb3VwX2J5KHN1Yiwgc2VzKSAlPiUgCiAgbXV0YXRlKHNlbGVjdGVkX3RhY2RhdGEgPSBtYXAodGFjZGF0YSwgfnZvbHVtZV93ZWlnaHRlZF9hdmVyYWdlX3RhY3MoLngsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2lvbnNfbGlzdCkpKQogIApgYGAKCgoKCiMjIENvbWJpbmluZyB0aGUgVEFDIGFuZCBibG9vZCAoaW5wdXQpIGRhdGEKCmBgYHtyfQptb2RlbGRhdGEgPC0gc2VsZWN0ZWRfdGFjcyAlPiUgCiAgc2VsZWN0KHNlczpyZWMsIHRhY2RhdGEgPSBzZWxlY3RlZF90YWNkYXRhKSAlPiUgCiAgaW5uZXJfam9pbihibG9vZHN0cmVhbV9kYXRhKQpgYGAKCgojIEZpdHRpbmcgVEFDcwoKIyMgUHJlcGFyYXRpb24KCiMjIyBDb3JyZWN0aW5nIFVuaXRzCgpUaGUgVEFDIGRhdGEgYXJlIGluIEJxL21MLCBhbmQgdGhlIGJsb29kc3RyZWFtIGRhdGEgYXJlIGluIGtCcS9tTC4gU28gd2UgY29ycmVjdCB0aGlzLgoKYGBge3J9Cm1vZGVsZGF0YSA8LSBtb2RlbGRhdGEgJT4lIAogIG11dGF0ZSh0YWNkYXRhID0gbWFwKHRhY2RhdGEsIH4ueCAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoYWNyb3NzKC5jb2xzID0gRnJvbnRhbDpIaXBwQW1nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfnVuaXRfY29udmVydCgueCwgZnJvbV91bml0cyA9ICJCcSIsIHRvX3VuaXRzID0gImtCcSIpKSkpKQpgYGAKCiMjIyBBZGRpbmcgZnJhbWUgbWlkdGltZXMgYW5kIGR1cmF0aW9ucwoKYGBge3J9Cm1vZGVsZGF0YSA8LSBtb2RlbGRhdGEgJT4lIAogIG11dGF0ZSh0YWNkYXRhID0gbWFwKHRhY2RhdGEsIH4ueCAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoZnJhbWVfc3RhcnQgPSBmcmFtZV9zdGFydCAvIDYwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyYW1lX2VuZCA9IGZyYW1lX2VuZCAvIDYwKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoZnJhbWVfZHVyID0gZnJhbWVfZW5kIC0gZnJhbWVfc3RhcnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJhbWVfbWlkID0gZnJhbWVfc3RhcnQgKyAwLjUqZnJhbWVfZHVyKSkpCmBgYAoKCiMjIyBBZGRpbmcgd2VpZ2h0cwoKYGBge3J9Cm1vZGVsZGF0YSA8LSBtb2RlbGRhdGEgJT4lIAogIG11dGF0ZSh0YWNkYXRhID0gbWFwKHRhY2RhdGEsIH4ueCAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUobWVhblRBQyA9IHJvd01lYW5zKCAueCAlPiUgc2VsZWN0KEZyb250YWw6SGlwcEFtZykgKSkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKHdlaWdodHMgPSB3ZWlnaHRzX2NyZWF0ZSh0X3N0YXJ0ID0gZnJhbWVfc3RhcnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRfZW5kID0gZnJhbWVfZW5kLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWMgPSBtZWFuVEFDKSkpKQpgYGAKCgojIyBGaXR0aW5nIGEgc2luZ2xlIFRBQwoKYGBge3J9CmZpdF8ydGMgPC0gdHdvdGNtKAogICAgICAgdF90YWMgPSBtb2RlbGRhdGEkdGFjZGF0YVtbMV1dJGZyYW1lX21pZCwgCiAgICAgICB0YWMgPSBtb2RlbGRhdGEkdGFjZGF0YVtbMV1dJEZyb250YWwsIAogICAgICAgaW5wdXQgPSBtb2RlbGRhdGEkaW5wdXRbWzFdXSwgCiAgICAgICB3ZWlnaHRzID0gbW9kZWxkYXRhJHRhY2RhdGFbWzFdXSR3ZWlnaHRzLCAKICAgICAgIHZCID0gMC4wNSwgbXVsdHN0YXJ0X2l0ZXIgPSA1KQoKZml0XzJ0YyRwYXIKCnBsb3QoZml0XzJ0YykKYGBgCgoKIyMgRml0dGluZyBtdWx0aXBsZSBUQUNzCgpIZXJlIHdlJ2xsIHVzZSBhIGxpbmVhcmlzZWQgbW9kZWwgYmVjYXVzZSB0aGV5IGZpdCBtb3JlIHF1aWNrbHksIGluIHRoaXMgY2FzZSBMb2dhbi4gIEJ1dCBtb3N0IGxpbmVhcmlzZWQgbW9kZWxzIHJlcXVpcmUgYSB0XCogdmFsdWUgdG8gb3BlcmF0ZS4KCiMjIyBTZWxlY3RpbmcgYSB0XCogdmFsdWUKCkxldCdzIGNob29zZSBhbiBhcHByb3ByaWF0ZSB0XCogdmFsdWUKCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9Cm1vZGVsZGF0YSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBmaWx0ZXIoc2VzPT0iYmFzZWxpbmUiKSAlPiUgCiAgc2xpY2UoMTo1KSAlPiUgCiAgbXV0YXRlKHRzdGFycGxvdCA9IG1hcDIodGFjZGF0YSwgaW5wdXQsIAogICAgIH5Mb2dhbl90c3RhcigKICAgICAgICAgdF90YWMgPSAueCRmcmFtZV9taWQsIAogICAgICAgICBsb3dyb2kgPSAgLngkSGlwcEFtZywKICAgICAgICAgbWVkcm9pID0gIC54JEZyb250YWwsCiAgICAgICAgIGhpZ2hyb2kgPSAueCRTdHJpYXR1bSwgCiAgICAgICAgIGlucHV0ID0gLnksCiAgICAgICAgIHZCID0gMC4wNSkKICAgICApKSAlPiUgCiAgcHVsbCh0c3RhcnBsb3QpCmBgYAoKT2ssIGxldCdzIHVzZSAxMyBmcmFtZXMuCgojIyMgRml0dGluZwoKTGV0J3MgZm9jdXMgb24gdGhlIGZyb250YWwgY29ydGV4CgpgYGB7cn0KbW9kZWxkYXRhIDwtIG1vZGVsZGF0YSAlPiUgCiAgZ3JvdXBfYnkoc3ViLCBzZXMpICU+JSAKICBtdXRhdGUoTG9nYW5fZml0ID0gbWFwMih0YWNkYXRhLCBpbnB1dCwgfkxvZ2FucGxvdCh0X3RhYyA9IC54JGZyYW1lX21pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFjID0gLngkRnJvbnRhbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlucHV0PSAueSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRzdGFySW5jbHVkZWRGcmFtZXMgPSAxMywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSAueCR3ZWlnaHRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdkIgPSAwLjA1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHVyID0gLngkZnJhbWVfZHVyKSkpCmBgYAoKCiMjIyBQbG90dGluZwoKTGV0J3Mgc2VlIGEgZmV3IGZpdHMKCmBgYHtyfQptYXAobW9kZWxkYXRhWzE6NixdJExvZ2FuX2ZpdCwgcGxvdCkKYGBgCgojIyMgT3V0Y29tZXMKCmBgYHtyfQpMb2dhbl9vdXRjb21lcyA8LSBtb2RlbGRhdGEgJT4lIAogIHNlbGVjdChzdWIsIHNlcywgTG9nYW5fZml0KSAlPiUgCiAgbXV0YXRlKFZ0ID0gbWFwX2RibChMb2dhbl9maXQsIGMoInBhciIsICJWdCIpKSkgJT4lIAogIHNlbGVjdCgtTG9nYW5fZml0KQoKZ2dwbG90KExvZ2FuX291dGNvbWVzLCBhZXMoeD1zZXMsIHk9VnQsIGNvbG91cj1zdWIsIGdyb3VwPXN1YikpICsKICBnZW9tX3BvaW50KHNpemU9MywgY29sb3VyPSJibGFjayIpICsKICBnZW9tX3BvaW50KHNpemU9Mi41KSArCiAgZ2VvbV9saW5lKCkgKyAKICBzY2FsZV95X2xvZzEwKCkKYGBgCgo=